<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

// namespace spica\core\view\html;

/**
 * Set of classes and functions for all user interface components in Spica.
 *
 * @category   spica
 * @package    core
 * @subpackage view\html
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      August 09, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Html.php 1869 2011-01-07 18:55:25Z pcdinh $
 */

/**
 * Gets HTML code to render page navigation menu that that manage
 * how users navigate from one page to another.
 *
 * namespace spica\core\view\html\page_nav;
 *
 * @param  int $totalItems
 * @param  int $currentPage
 * @param  int $itemPerPage
 * @return string
 */
function spica_html_page_nav($totalItems, $currentPage = 1, $itemPerPage = 20)
{
    // FIXME
}

/**
 * Converts array of tag attribute names and values to string.
 *
 * namespace spica\core\view\html\attributes;
 *
 * @param  array $options array of strings
 * @return string
 */
function spica_html_attributes($options)
{
    if (false === empty($options))
    {
        $html = array();

        foreach ($options as $key => $value)
        {
            $html[] = $key.'="'.htmlspecialchars($value, ENT_COMPAT, 'UTF-8').'"';
        }

        sort($html);
        $html = implode(" ", $html);
        return ' '.$html;
    }

    return '';
}

/**
 * Generates an obfuscated version of an email address.
 *
 * namespace spica\core\view\html\email;
 *
 * @param  string $email email address
 * @return string
 */
function spica_html_email($email)
{
    $obfuscated = '';
    $letters    = str_split($email);
    foreach ($letters as $letter)
    {
        switch (($letter === '@') ? rand(1, 2) : rand(1, 3))
        {
            case 1:
            // HTML entity code
                $obfuscated .= '&#'.ord($letter).';';
                break;

            case 2:
            // Hex character code
                $obfuscated .= '&#x'.dechex(ord($letter)).';';
                break;

            case 3:
            // Raw (no) encoding
                $obfuscated .= $letter;
        }
    }

    return $obfuscated;
}

/**
 * Creates an email anchor.
 *
 * namespace spica\core\view\html\mailto;
 *
 * @param  string $email email address to send to
 * @param  string $title link text
 * @param  array  $attributes HTML anchor attributes
 * @return string
 */
function spica_html_mailto($email, $title = null, $attributes = null)
{
    if (true === empty($email))
    {
        return $title;
    }

    // Remove the subject or other parameters that do not need to be encoded
    if (false !== strpos($email, '?'))
    {
        // Extract the parameters from the email address
        list ($email, $params) = explode('?', $email, 2);

        // Make the params into a query string, replacing spaces
        $params = '?'.str_replace(' ', '%20', $params);
    }
    else
    {
        // No parameters
        $params = '';
    }

    // Obfuscate email address
    $obfuscated = spica_html_email($email);

    // Title defaults to the encoded email address
    if (true === empty($title))
    {
        $title = $obfuscated;
    }

    // Parse attributes
    if (true === empty($attributes))
    {
        $attributes = spica_html_attributes($attributes);
    }

    // Encoded start of the href="" is a static encoded version of 'mailto:'
    return '<a href="&#109;&#097;&#105;&#108;&#116;&#111;&#058;'.$obfuscated.$params.'"'.$attributes.'>'.$title.'</a>';
}

/**
 * Returns a string as HTML form option list to place in a select list <select></select>.
 *
 * namespace spica\core\view\html\options;
 *
 * @param  array        $dataSet  where key are labels and value are values with
 * @param  string|array $selected The value or values in $dataSet would be preselected
 * @return string HTML output of the select options
 */
function spica_html_options($dataSet, $selected)
{
    $html     = '';
    $selected = (array) $selected;

    if (false === is_array($dataSet))
    {
        trigger_error('$dataSet must be array()', E_USER_NOTICE);
        return false;
    }

    foreach ($dataSet as $value => $label)
    {
        $html .= '<option value="'.$value.'"'.(true === in_array($value, $selected) ? ' selected="selected"' : '').'>'.htmlspecialchars($label).'</option>'."\n";
    }

    return $html;
}

/**
 * Returns a string as a HTML select and its options.
 *
 * namespace spica\core\view\html\select;
 *
 * @param  array  $dataSet where key are labels and value are values
 * @param  string $selected name of the key in $dataSet would be preselected
 * @param  string $attr HTML attributes of the select tag
 * @return string HTML output
 */
function spica_html_select($dataSet, $selected = null, $attr = null)
{
    $attr = spica_html_attributes($attr);
    $html = spica_html_options($dataSet, $selected);
    return '<select'.$attr.'>'.$html.'</select>'."\n";
}

/**
 * Returns a string as a HTML select and its options but this control allows
 * more than one choice to be selected. Usually done by holding down the CTRL
 * key while selecting.
 *
 * namespace spica\core\view\html\select_multiple;
 *
 * @param  array  $dataSet where key are labels and value are values
 * @param  string $selected name of the key in $dataSet would be preselected
 * @param  string $attr HTML attributes of the select tag
 * @return string HTML output
 */
function spica_html_select_multiple($dataSet, $selected = null, $attr = null)
{
    $attr['multiple'] = 'multiple';
    $attr = spica_html_attributes($attr);
    $html = spica_html_options($dataSet, $selected);
    return '<select'.$attr.'>'.$html.'</select>'."\n";
}

/**
 * Gets HTML code to render a HTML <select></select> and its options
 * which is organized into groups <optgroup></optgroup>.
 *
 * namespace spica\core\view\html\optgroup_select;
 *
 * @param  mixed $dataSet   An array or SpicaDataSet object.
 * @param  string $selected Name of the key in $dataSet would be preselected
 * @param  array $attributes HTML select tag attributes
 * @return string
 */
function spica_html_optgroup_select($dataSet, $selected, $attributes = array())
{
    if (false === is_array($dataSet))
    {
        throw new SpicaTemplateException('$dataSet must be an array. ');
    }

    $attributes = spica_html_attributes($attributes);
    $output     = '';

    if (false === empty($values))
    {
        foreach ($dataSet as $key => $value)
        {
            if (true === is_array($value))
            {
                $output .= '<optgroup label="'.$key.'">'."\n";
                $output .= spica_html_options($dataSet, $selected)."\n";
                $output .= '</optgroup>'."\n";
            }
            else
            {
                $sel     = ($selected == $key) ? ' selected="selected"' : '';
                $output .= '<option value="'.$key.'"'.$sel.'>'.$value.'</option>'."\n";
            }
        }
    }

    return '<select'.$attributes.'>'."\n".$output."</select>\n";
}

/**
 * Gets HTML code to render a HTML select and its options for hours.
 *
 * @param  mixed  $fieldName HTML name attribute
 * @param  string $selected  Name of the key in $dataSet would be preselected
 * @param  array  $attr      HTML attributes of the select tag
 * @return string
 */
function spica_html_select_hour($fieldName, $selected = null, $attr = null)
{
    $dataSet   = array();
    for ($hour = 0; $hour < 24; $hour++)
    {
        $dataSet[$hour] = $hour;
    }

    return spica_html_form_select($fieldName, $dataSet, $selected, $attr);
}

/**
 * Gets HTML code to render a HTML select and its options for minutes.
 *
 * @param  mixed  $fieldName HTML name attribute
 * @param  string $selected  Name of the key in $dataSet would be preselected
 * @param  array  $attr      HTML attributes of the select tag
 * @return string
 */
function spica_html_select_minute($fieldName, $selected = null, $attr = null)
{
    $dataSet = array();

    for ($minute = 0; $hour < 60; $minute++)
    {
        $dataSet[$minute] = $minute;
    }

    return spica_html_form_select($fieldName, $dataSet, $selected, $attr);
}

/**
 * Gets HTML code to render a HTML select and its options
 * for week days (Monday, Tuesday ...).
 *
 * @param  mixed  $fieldName HTML name attribute
 * @param  string $selected  Name of the key in $dataSet would be preselected
 * @param  int    $firstDay  What is first day of the week: 0 - Sunday, 1 - Monday
 * @param  array  $attr      HTML attributes of the select tag
 * @return string
 */
function spica_html_select_weekday($fieldName, $selected = null, $firstDay = 1, $attr = null)
{
    if (1 === $firstDay)
    {
        $weekDays = array('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday');
    }
    else
    {
        $weekDays = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
    }

    return spica_html_form_select($fieldName, $weekDays, $selected, $attr);
}

/**
 * Gets HTML code to render a HTML select and its options
 * for month names (January, February, March ...).
 *
 * @param  mixed  $fieldName HTML name attribute
 * @param  string $selected  Name of the key in $dataSet would be preselected
 * @param  int    $firstDay  What is first day of the week: 0 - Sunday, 1 - Monday
 * @param  array  $attr      HTML attributes of the select tag
 * @return string
 */
function spica_html_select_month_names($fieldName, $selected = null, $firstDay = 1, $attr = null)
{
    $monthNames = array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
    return spica_html_form_select($fieldName, $monthNames, $selected, $attr);
}

/**
 * Gets HTML code to render a HTML list (with <ul> or <ol>).
 *
 * @param  array  $data      Data to build a list
 * @param  mixed  $fieldName List type: ul (un-ordered list) or ol (ordered list)
 * @param  array  $attr      HTML attributes of the list tag
 * @return string
 */
function spica_html_list($data = array(), $type = 'ul', $attr = array())
{
    if (false === in_array($type, array('li', 'ul')))
    {
        throw new InvalidArgumentException('List type must be of "ul" or "ol" only');
    }

    $attr = spica_html_attributes($attr);
    return '<'.$type.$attr.'>'.implode('</li><li>', $data).'</li></'.$type.'>';
}

/**
 * Generates HTML code for rendering a radio input.
 *
 * @param  string     $name    Input name attribute
 * @param  string|int $value   Radio input value
 * @param  array      $checked Default value to check
 * @param  array      $attr    HTML attributes
 * @return string
 */
function spica_html_radio($name, $value, $checked = null, $attr = array())
{
    $attr['type'] = 'radio';
    return spica_html_input($name, $value, $checked, $attr);
}

/**
 * Generates HTML code for rendering a checkbox input.
 *
 * @param  string     $name    Input name attribute
 * @param  string|int $value   Checkbox input value
 * @param  array      $checked Default value to check
 * @param  array      $attr    HTML attributes
 * @return string
 */
function spica_html_checkbox($name, $value, $checked = null, $attr = array())
{
    $attr['type'] = 'checkbox';
    return spica_html_input($name, $value, $checked, $attr);
}

/**
 * Generates HTML code for rendering a text input.
 *
 * @param  string $name  Input name attribute
 * @param  string $value Input value, defaults to empty string.
 * @param  array  $attr  HTML attributes
 * @return string
 */
function spica_html_input_text($name, $value = '', $attr = array())
{
    $attr['type'] = 'text';
    return spica_html_input($name, $value, null, $attr);
}

/**
 * Generates HTML code for rendering a password input.
 *
 * @param  string $name  Input name attribute
 * @param  string $value Input value, defaults to empty string.
 * @param  array  $attr  HTML attributes
 * @return string
 */
function spica_html_input_password($name, $value = '', $attr = array())
{
    $attr['type'] = 'password';
    return spica_html_input($name, $value, null, $attr);
}

/**
 * Generates HTML code for rendering a generic input.
 *
 * @param  string     $name    Input name attribute
 * @param  string|int $value   Input value
 * @param  array      $checked Default value to check, applicable for radio and checkbox only
 * @param  array      $attr    HTML attributes
 * @return string
 */
function spica_html_input($name, $value, $checked = null, $attr = array())
{
    if (false === isset($attr['type']))
    {
        throw new InvalidArgumentException('Attribute "type" must be specified. ');
    }

    switch ($attr['type'])
    {
        case 'radio':
        case 'checkbox':
            $ifChecked = (true === isset($_POST[$name]) && $_POST[$name] == $value) || ($checked == $value);
            $checked   = $ifChecked?' checked="checked"':'';
            break;

        default:
            $checked = '';
            if (true === isset($attr['checked']))
            {
                unset($attr['checked']);
            }
            break;
    }

    // Parse attributes
    $attr = spica_html_attributes($attr);
    return '<input name="'.$name.'" value="'.spica_html_escape_quotes($value).'"'.$checked.$attr.' />';
}

/**
 * Represents an HTML input element of type <code>radio</code> or <code>checkbox</code>.
 * The radio/checkbox will be rendered as checked, or not, based on the value of the value property.
 *
 * @param  array  $input
 * @param  string $type Either radio or checkbox. Defaults to radio
 * @param  string $layout
 * @return string
 */
function spica_html_boolean_input($input, $type = 'radio', $layout = null)
{
    $left  = sprintf('<input type="%s" name="%s"%s value="%s" />%s</input>', $type, $input[0]['name'], (1 === $input[0]['checked']) ? ' checked="checked"' : '', $input[0]['value'], $input[0]['text']);
    $right = sprintf('<input type="%s" name="%s"%s value="%s" />%s</input>', $type, $input[1]['name'], (1 === $input[1]['checked']) ? ' checked="checked"' : '', $input[1]['value'], $input[1]['text']);

    if (null === $layout)
    {
        return $left.$right;
    }

    return sprintf($layout, $left, $right);
}

/**
 * Represents an HTML input element of type <code>checkbox</code>.
 * The checkbox will be rendered as checked, or not, based on the value of the value property.
 *
 * @param  array  $input
 * @param  string $layout
 * @return string
 */
function spica_html_boolean_checkbox($input, $layout = null)
{
    if (2 !== count($input))
    {
        return 'The first paremeter to spica_html_boolean_checkbox() must be an array that contains 2 sub-arrays as its elements';
    }

    return spica_html_boolean_input($input, 'checkbox', $layout);
}

/**
 * Represents an HTML input element of type <code>radio</code>.
 * The radio will be rendered as checked, or not, based on the value of the value property.
 *
 * @param  array  $input
 * @param  string $layout
 * @return string
 */
function spica_html_boolean_radio($input, $layout = null)
{
    if (2 !== count($input))
    {
        return 'The first paremeter to spica_html_boolean_radio() must be an array that contains 2 sub-arrays as its elements';
    }

    return spica_html_boolean_input($input, 'radio', $layout);
}

/**
 * Generates HTML code for rendering a textarea.
 *
 * @param  string $name  Textarea name attribute
 * @param  string $value Textarea content
 * @param  array  $attr  HTML attributes
 * @return string
 */
function spica_html_textarea($name, $value, $attr = array())
{
    // Parse attributes
    $attr = spica_html_attributes($attr);
    return '<textarea name="'.$name.'"'.$attr.'>'.$value.'</textarea>';
}

/**
 * Generates HTML code for including a CSS link tag.
 *
 * @param  string $name      CSS file name (.css extension excluded)
 * @param  string $component Component name
 * @return string
 */
function spica_html_css_link($name, $component = null)
{
    if (null !== $component)
    {
        $path = 'public/components/'.$component.'/css/'.$name.'.css';
    }
    else
    {
        $path = SpicaContext::getCssPath().'/'.$name.'.css';
    }

    return '<link type="text/css" rel="stylesheet" href="'.$path.'" />';
}

/**
 * Generates JavaScript code for dynamically adding a JavaScript link tag to the head of the HTML page.
 *
 * @param  string $name      JS file name (.js extension excluded)
 * @param  string $component Component name
 * @return string
 */
function spica_html_css_head($name, $component = null)
{
    if (null !== $component)
    {
        $path = 'public/components/'.$component.'/js/'.$name.'.js';
    }
    else
    {
        $path = SpicaContext::getJsPath().'/'.$name.'.js';
    }

    return '<script type="text/javascript"> var c = document.createElement(\'link\'); c.type = \'text/css\'; c.rel = \'stylesheet\'; s.href = \''.$path.'\'; cssNode.media = \'screen\'; document.getElementsByTagName("head")[0].appendChild(s);</script>';
}

/**
 * Generates HTML code for including a JavaScript link tag.
 *
 * @param  string $name      JS file name (.js extension excluded)
 * @param  string $component Component name
 * @return string
 */
function spica_html_js_link($name, $component = null)
{
    if (null !== $component)
    {
        $path = 'public/components/'.$component.'/js/'.$name.'.js';
    }
    else
    {
        $path = SpicaContext::getJsPath().'/'.$name.'.js';
    }

    return '<script type="text/javascript" src="'.$path.'"></script>';
}

/**
 * Generates JavaScript code for dynamically adding a JavaScript link tag to the head of the HTML page.
 *
 * @param  string $name      JS file name (.js extension excluded)
 * @param  string $component Component name
 * @return string
 */
function spica_html_js_head($name, $component = null)
{
    if (null !== $component)
    {
        $path = 'public/components/'.$component.'/js/'.$name.'.js';
    }
    else
    {
        $path = SpicaContext::getJsPath().'/'.$name.'.js';
    }

    return '<script type="text/javascript"> var s = document.createElement(\'script\'); s.type = \'text/javascript\'; s.src = \''.$path.'\';document.getElementsByTagName("head")[0].appendChild(s);</script>';
}

/**
 * Represents an HTML attribute <code>checked="checked"</code> for a radio input.
 *
 * @param  string $attrValue The value of the radio input
 * @param  string $default The default value to check when user input is not available
 * @param  array  $input User input, default to null
 * @return string
 */
function spica_html_attr_checked($attrValue, $default, $input = null)
{
    return ($input === $attrValue || (null === $input && $attrValue === $default)) ? ' checked="checked"' : null;
}

/**
 * Returns one or more HTML anchor HTML string.
 *
 * @param  array      $input User input
 * @param  int|string $href href field name to build anchor URL value (used with $urlPattern)
 * @param  int|string $title title field name
 * @param  string     $urlPattern URL pattern
 * @param  array      $attr HTML attributes
 * @param  string     $sep HTML anchor separator
 * @return string|null
 */
function spica_html_anchor($input, $href, $title, $urlPattern, $attr = null, $sep = null)
{
    if (empty($input))
    {
        return null;
    }
    
    if (empty($attr))
    {
        $attr = spica_html_attributes($attr);
    }

    // 2-dimentional array
    if (true === isset($input[0]))
    {
        $a = array();
        
        foreach ($input as $row)
        {
            $url = sprintf($urlPattern, $row[$href]);
            $a[] = '<a href="'.$url.'"'.$attr.'>'.$row[$title].'</a>';
        }

        return implode($sep, $a);
    }

    return sprintf('<a href="%s"%s>%s</a>', sprintf($urlPattern, $input[$href]), $attr, $input[$title]);
}

/**
 * Transforms quotes (" and ') into HTML entities (&quot; and &#39;).
 *
 * @param  string $html
 * @return string
 */
function spica_html_escape_quotes($html)
{
    return str_replace(array('\"', '"', "\'", "'"), array('&quot;', '&quot;', '&#39;', '&#39;'), $html);
}

/**
 * Transforms HTML quote entities (&quot; and &#39;) into plain text quotes (" and ').
 *
 * @param  string $html
 * @return string
 */
function spica_html_unescape_quotes($html)
{
    return str_replace(array('&quot;', '&#39;'), array('"', "'"), $html);
}

/**
 * Gets the HTML code for Google Analytics for embedding into web pages.
 *
 * @return string
 */
function spica_html_ga($key)
{
    $return = '';
    $return .= '<script type="text/javascript">' . "\n";
    $return .= 'var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");' . "\n";
    $return .= 'document.write(unescape("%3Cscript src=\'" + gaJsHost + "google-analytics.com/ga.js\' type=\'text/javascript\'%3E%3C/script%3E"));' . "\n";
    $return .= '</script>' . "\n";
    $return .= '<script type="text/javascript">' . "\n";
    $return .= 'try {' . "\n";
    $return .= 'var pageTracker = _gat._getTracker("' . $key . '");' . "\n";
    $return .= 'pageTracker._trackPageview();' . "\n";
    $return .= '} catch(err) {' . "\n";
    $return .= '}' . "\n";
    $return .= '</script>' . "\n";
    return $return;
}

/**
 * Highlight word(s) within a string.
 *
 * @param string $string String input
 * @param string $words The word(s) to be highlighted
 * @param string $openTag HTML tag added right before the word
 * @param string $closeTag HTML tag added right after the word
 * @return string
 */
function spica_html_highlight($string, $words, $openTag = '<span style="font-weight:bold;background-color:#ffff00">', $closeTag = '</span>')
{
    return preg_replace('/(' . preg_quote($words) . ')/i', $openTag . "\\1" . $closeTag, $string);
}

/**
 * Encode double quotes, single quotes to be able to insert into a inline Javascript expression
 * like onlick="", onhover="".
 *
 * @param  string $str
 * @return string String like "\x20\x6e\x6f\x74\x20\x74\x6f\x20"
 */
function spica_html_js_escape($str)
{
    $buff = '';
    
    for ($i = 0, $len = strlen($str); $i < $len; $i++)
    {
        $buff .= (ord(substr($str, $i, 1)) < 16 ? '\\x0' : '\\x') . dechex(ord(substr($str, $i, 1)));
    }

    return $buff;
}

/**
 * A set of helper methods for generating commonly used HTML code .
 *
 * namespace spica\core\view\helper\Html;
 *
 * @category   spica
 * @package    core
 * @subpackage view\helper
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      March 31, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Html.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaHtml
{
    /**
     * Generates HTML code for rendering a bullet (unordered) list.
     *
     * @param  SpicaArrayList $array
     * @param  string         $textField
     * @param  string         $idField
     * @param  int            $activeId
     * @param  array          $attr
     * @return string
     */
    public static function bulletList(SpicaArrayList $array, $textField, $idField = null, $activeId = null, $attr = null)
    {
        return self::_list('ul', $array, $textField, $idField, $activeId, $attr);
    }

    /**
     * Generates HTML code for rendering a numbered (ordered) list.
     *
     * @param  SpicaArrayList $array
     * @param  string         $textField
     * @param  string         $idField
     * @param  int            $activeId
     * @param  array          $attr
     * @return string
     */
    public static function orderedList(SpicaArrayList $array, $textField, $idField = null, $activeId = null, $attr = null)
    {
        return self::_list('ol', $array, $textField, $idField, $activeId, $attr);
    }

    /**
     * Generates HTML code for rendering option groups in a select list.
     *
     * @param  SpicaArrayList $values The database resultset which is ordered by column $groupField
     * @param  string $groupField The data field name for group label
     * @param  string $valueField The data field name for option value
     * @param  string $labelField The data field name for option label
     * @param  string $selected   The option value that is selected by user
     * @return string
     */
    public static function optionGroups(SpicaArrayList $values, $groupField, $valueField, $labelField, $selected = null)
    {
        $html   = '';
        $groups = array();
        $open   = false;

        foreach ($values as $key => $entry)
        {
            $value  = $entry->get($valueField);
            $option = '<option value="'.$value.'"'.($value == $selected?' selected="selected"':'').'>'.htmlspecialchars($entry->get($labelField)).'</option>'."\n";
            $group  = $entry->get($groupField);
            $groups[$group][] = $option;
        }

        foreach ($groups as $name => $options)
        {
            $html .= '<optgroup label="'.$name.'">'.implode("\n", $options).'</optgroup>';
        }

        return $html;
    }

    /**
     * Generates HTML code for rendering a select options list.
     *
     * @param  SpicaArrayList $values The database resultset which is ordered by column $groupField
     * @param  string $valueKey    The data field name for option value
     * @param  string $selectedKey The data field name for option label
     * @param  string $selectedKey The option value that is selected by user
     * @param  array  $attr        The select tag attributes
     * @param  array  $guideOption List of values to be used for populating additional options that is on the top of the selection
     * @return string
     */
    public static function selectList(SpicaArrayList $values, $valueKey, $labelKey, $selectedKey = null, $attr = array(), $guideOption = array())
    {
        // Parse attributes
        $attr     = spica_html_attributes($attr);
        $options  = spica_html_options($guideOption, $selectedKey);
        $options .= self::options($values, $valueKey, $labelKey, $selectedKey);
        return '<select'.$attr.'>'.$options.'</select>';
    }

    /**
     * Generates HTML code for rendering a select options list which allows
     * more than one choice to be selected.
     *
     * @param  SpicaArrayList $values The database resultset which is ordered by column $groupField
     * @param  string $valueKey    The data field name for option value
     * @param  string $selectedKey The data field name for option label
     * @param  string $selectedKey The option value that is selected by user
     * @param  array  $attr        The select tag attributes
     * @param  array  $guideOption List of values to be used for populating additional options that is on the top of the selection
     * @return string
     */
    public static function selectMultiple(SpicaArrayList $values, $valueKey, $labelKey, $selectedKey = null, $attr = array(), $guideOption = array())
    {
        $attr['multiple'] = 'multiple';
        // Parse attributes
        $attr     = spica_html_attributes($attr);
        $options  = spica_html_options($guideOption, $selectedKey);
        $options .= self::options($values, $valueKey, $labelKey, $selectedKey);
        return '<select'.$attr.'>'.$options.'</select>';
    }

    /**
     * Generates a list of HTML option tags based on the data provided.
     *
     * @throws InvalidArgumentException
     * @param  SpicaAbstractDataSet $values
     * @param  string $valueKey    The array key to retrieve option value
     * @param  string $labelKey    The array key to retrieve option label
     * @param  string $selected    The value that the user selected and submited before the re-rendering of this form happens
     * @param  array  $guideOption The guide option to use the list
     * @return string
     */
    public static function options($values, $valueKey, $labelKey, $selected = null, $guideOption = array())
    {
        if (false === $values->isReadable())
        {
            throw new InvalidArgumentException('Error: The provided DataSet to populate option list contains errors. ');
        }

        $selected = (array) $selected;

        if (false === empty($guideOption))
        {
            $html = spica_html_options($guideOption, $selected);
        }
        else
        {
            $html = '';
        }

        foreach ($values as $entry)
        {
            $value = $entry->get($valueKey);
            $html .= '<option value="'.$value.'"'.(true === in_array($value, $selected) ? ' selected="selected"' : '').'>'.htmlspecialchars($entry->get($labelKey)).'</option>'."\n";
        }

        return $html;
    }

    /**
     * Generates HTML code for rendering a radio input.
     *
     * @param  string     $name    Input name attribute
     * @param  string|int $value   Radio input value
     * @param  array      $checked Default value to check
     * @param  array      $attr    HTML attributes
     * @return string
     */
    public static function radio($name, $value, $checked = null, $attr = array())
    {
        return spica_html_radio($name, $value, $checked, $attr);
    }

    /**
     * Generates HTML code for rendering a checkbox input.
     *
     * @param  string     $name    Input name attribute
     * @param  string|int $value   Checkbox input value
     * @param  array      $checked Default value to check
     * @param  array      $attr    HTML attributes
     * @return string
     */
    public static function checkbox($name, $value, $checked = null, $attr = array())
    {
        return spica_html_checkbox($name, $value, $checked, $attr);
    }

    /**
     * Generates HTML code for rendering a text input.
     *
     * @param  string $name  Input name attribute
     * @param  string $value Input value, defaults to an empty string.
     * @param  array  $attr  HTML attributes. Can be null
     * @return string
     */
    public static function textInput($name, $value = '', $attr = array())
    {
        $value = (isset($_POST[$name])) ? $_POST[$name] : $value;
        return spica_html_input_text($name, $value, $attr);
    }

    /**
     * Generates HTML code for rendering a password input.
     *
     * @param  string $name  Input name attribute
     * @param  string $value Input value, defaults to an empty string.
     * @param  array  $attr  HTML attributes. Can be null
     * @return string
     */
    public static function passwordInput($name, $value = '', $attr = array())
    {
        $value        = (isset($_POST[$name])) ? $_POST[$name] : $value;
        $attr['type'] = 'password';
        return spica_html_input($name, $value, $attr);
    }

    /**
     * Generates HTML code for rendering a generic input (text, radio, checkbox or hidden).
     *
     * @param  string     $name    Input name attribute
     * @param  string|int $value   Input value
     * @param  array      $checked Default value to check, applicable for radio and checkbox only.
     * @param  array      $attr    HTML attributes
     * @return string
     */
    public static function input($name, $value, $checked = null, $attr = array())
    {
        return spica_html_input($name, $value, $checked, $attr);
    }

    /**
     * Generates HTML code for rendering a textarea.
     *
     * @param  string $name  Textarea name attribute
     * @param  string $value Textarea content, defaults to an empty string
     * @param  array  $attr  HTML attributes
     * @return string
     */
    public static function textarea($name, $value = '', $attr = array())
    {
        $value = (isset($_POST[$name])) ? $_POST[$name] : $value;
        return spica_html_textarea($name, $value, $attr);
    }

    /**
     * Generates HTML code for doc type.
     *
     * @param  string $type Possible values: 's' for XHTML 1.1 Strict, 't' for XHTML 1.0 Transitional and 'w' for XHTML Mobile 1.1
     * @return string
     */
    public static function doctype($type = 's')
    {
        switch ($type)
        {
            case 't':
                return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'."\n";

            case 's':
                return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'."\n";

            case 'w':
                return '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">'."\n";
        }
    }

    /**
     * Replaces quotes with HTML entities to make it possible to put them into
     * attribute values.
     *
     * @param  string $str
     * @return string
     */
    public static function escapeQuotes($str)
    {
        return spica_html_escape_quotes($str);
    }

    /**
     * Replaces HTML entities for quotes with real quotes (" and ').
     *
     * @param  string $str
     * @return string
     */
    public static function unescapeQuotes($str)
    {
        return spica_html_unescape_quotes($str);
    }

    /**
     * Escapes special HTML characters for embedding them with Flash tags.
     *
     * If you are building a loadvars page for Flash and have problems with special chars
     * such as " & ", " ' " etc, you should escape them for flash.
     *
     * Try trace(escape("&")); in flash' actionscript to see the escape code for &;
     *
     *   % = %25
     *   & = %26
     *   ' = %27
     *
     * @author realcj at g mail dt com
     * @param  string $string
     * @return string
     */
    public static function encodeFlashEntities($string)
    {
        return str_replace(array("&", "'"), array("%26", "%27"), $string);
    }

    /**
     * Converts string to HTML entities.
     *
     * @author eric.wallet at yahoo.fr
     * @param  string $str
     * @return string
     */
    public static function encodeHtmlNumericEntities($str)
    {
        return preg_replace('/[^!-%\x27-;=?-~ ]/e', '"&#".ord("$0").chr(59)', $str);
    }

    /**
     * Converts HTML entities back to normal string.
     *
     * @author eric.wallet@yahoo.fr
     * @param  string $str
     * @return string
     */
    public static function decodeHtmlNumericEntities($str)
    {
        return utf8_encode(preg_replace('/&#(\d+);/e', 'chr(str_replace(";","",str_replace("&#","","$0")))', $str));
    }

    /**
     * Generates HTML code for rendering a HTML list.
     *
     * @param  string         $tag Tag name ul or ol
     * @param  SpicaArrayList $array
     * @param  string         $textField
     * @param  string         $idField
     * @param  int            $activeId
     * @param  array          $attr
     * @return string
     */
    protected static function _list($tag, $array, $textField, $idField = null, $activeId = null, $attr = null)
    {
        $attr = spica_html_attributes($attr);
        $list = '';

        foreach ($array as $entry)
        {
            if (null  !== $idField && $entry->get($idField) == $activeId)
            {
                $list .= '<li class="active">'.$entry->get($textField).'</li>';
            }
            else
            {
                $list .= '<li>'.$entry->get($textField).'</li>';
            }
        }

        return '<'.$tag.$attr.'>'.$list.'</'.$tag.'>';
    }
}

/**
 * A <code>SpicaPageNavigation</code> is a software module which is used to generate HTML tags
 * that manage how users navigate from one page to another.
 *
 * @category   spica
 * @package    core
 * @subpackage view\helper
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 16, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Html.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaPageNavigation
{
    /**
     * The current page.
     *
     * @var int
     */
    private $_currentPage;

    /**
     * Items per page
     *
     * @var int
     */
    private $_perPage;

    /**
     * Total items
     *
     * @var int
     */
    private $_totalItems;

    /**
     * Total pages
     *
     * @var int
     */
    private $_totalPages;

    /**
     * URL string template (with placeholder).
     *
     * @var string
     */
    private $_urlTemplate = '';

    /**
     * Constructs an object of <code>SpicaPageNavigation</code>.
     *
     * @param string $urlTemplate A string like "cp/category/list/page/{#}"
     * @param int    $totalItems
     * @param int    $currentPage
     * @param int    $itemPerPage
     */
    public function __construct($urlTemplate, $totalItems, $currentPage = 1, $itemPerPage = 10)
    {
        $this->_urlTemplate = $urlTemplate;
        $this->_totalItems  = (int) max(0, $totalItems);
        $this->_perPage     = (int) max(1, $itemPerPage);
        $this->_totalPages  = (int) ceil($this->_totalItems/$itemPerPage);
        // Page can not be smaller than 1 and not greater than max page
        $this->_currentPage = (int) min(max(1, $currentPage), max(1, $this->_totalPages));

        $this->_firstItem   = (int) min((($this->_currentPage - 1) * $this->_perPage) + 1, $this->_totalItems);
        $this->_lastItem    = (int) min($this->_firstItem + $this->_perPage - 1, $this->_totalItems);

        // If there is no first/last/previous/next page, relative to the
        // current page, value is set to FALSE. Valid page number otherwise.
        $this->_firstPage   = ($this->_currentPage === 1) ? false : 1;
        $this->_lastPage    = ($this->_currentPage >= $this->_totalPages) ? false : $this->_totalPages;
        $this->_prevPage    = ($this->_currentPage > 1) ? $this->_currentPage - 1 : false;
        $this->_nextPage    = ($this->_currentPage < $this->_totalPages) ? $this->_currentPage + 1 : false;
    }

    /**
     * Gets total pages.
     *
     * @return int
     */
    public function getTotalPages()
    {
        return $this->_totalPages;
    }

    /**
     * Gets HTML tags for page navigation.
     *
     * @param  int    $type   Paging type
     * @param  string $spacer Page node separator
     * @return string
     */
    public function output($type = 1, $spacer = ' | ')
    {
        switch ($type)
        {
            case 1:
                return $this->_outputLikeClassic();

            case 2:
                return $this->_outputLikeForum();

            case 3:
                return $this->_outputLikeSocialNews();

            case 4:
                return $this->_outputLikeConcise();

            default:
                return $this->_outputAll($spacer);
        }
    }

    /**
     * Outputs page navigation in forum style.
     * @example Pages: 1 … 4 5 6 7 8 … 15
     */
    protected function _outputLikeForum()
    {
        include dirname(dirname(__FILE__)).'/templates/paging/forum.tpl.php';
    }

    /**
     * Outputs page navigation in social news style.
     *
     * @example  « Previous  1 2 … 5 6 7 8 9 10 11 12 13 14 … 25 26  Next »
     */
    protected function _outputLikeSocialNews()
    {
        include dirname(dirname(__FILE__)).'/templates/paging/socialnews.tpl.php';
    }

    /**
     * Outputs page navigation in social news style.
     *
     * @example ‹ First  < 1 2 3 >  Last ›
     */
    protected function _outputLikeClassic()
    {
        include dirname(dirname(__FILE__)).'/templates/paging/classic.tpl.php';
    }

    /**
     * Outputs page navigation in concise style.
     *
     * @example « Previous | Page 2 of 11 | Showing items 6-10 of 52 | Next »
     */
    protected function _outputLikeConcise()
    {
        include dirname(dirname(__FILE__)).'/templates/paging/concise.tpl.php';
    }

    /**
     * Outputs paging HTML code.
     *
     * @param  string $spacer Page node separator
     * @return string
     */
    protected function _outputAll($spacer)
    {
        $str  = '';
        $i    = 1;
        $str .= '<a href="'.str_replace('{#}', 1, $this->_urlTemplate).'"> &lt;&lt; </a>';
        $str .= $spacer;
        $str .= '<a href="'.str_replace('{#}', $i, $this->_urlTemplate).'"> &lt; </a>';
        $str .= $spacer;

        $page  = $this->_currentPage;
        $total = $this->_totalPages;

        for ($i = 1; $i <= $total; $i++)
        {
            if ($i === $page)
            {
                $str .= ' '.$i;
            }
            else
            {
                $str .= '<a href="'.str_replace('{#}', $i, $this->_urlTemplate).'">'.$i.'</a>';
            }

            $str .= $spacer;
        }

        $str .= '<a href="'.str_replace('{#}', $page + 1, $this->_urlTemplate).'"> &gt; </a>';
        $str .= $spacer;
        $str .= '<a href="'.str_replace('{#}', $total, $this->_urlTemplate).'"> &gt;&gt; </a>';

        return $str;
    }
}

?>